/*
	Prototype is developed by Benato Denis.
	Prototype is a project started on 23/09/2013.
	Prototype is release under LGPLv3 license.
	If you use part of Prototype software on other
	projects, you must copy this comment into the source
	code of the other projects, and quote Benato Denis 
	in the credits of the other software.
*/

#include "Prototype.h"

#include "Program.h"

using namespace std;

Program::Program()
{
	this->ProgramMemory = new Memory;
	this->Loaded = false;
	this->ProgramExecutable = false;
	this->Instructions = (struct Instruction*)NULL;
	this->ConditionalStructures = (struct Redirections*)NULL;
}

struct ProgramInfo* Program::GetProgramInfo()
{
	if (this->Loaded)
	{
		struct ProgramInfo* toReturn = (struct ProgramInfo*)malloc(sizeof(struct ProgramInfo));
		memcpy(toReturn, &this->CurrentProgramInfo, sizeof(struct ProgramInfo));
		return toReturn;
	} else {
		return (struct ProgramInfo*)NULL;
	}
}

void Program::Load(char *file)
{
	this->ProgramMemory->Reset();
	FILE *pFile;
	pFile = fopen(file, "r");
	unsigned long pSize = 0;
	fseek(pFile, 0, SEEK_END);
	pSize = (unsigned long)ftell(pFile);
	fseek(pFile, 0, SEEK_SET);
	fread(&this->CurrentProgramInfo, sizeof(struct ProgramInfo), 1, pFile);
	fread(&this->CompiledPrototypeVersion, sizeof(struct PrototypeVersion), 1, pFile);
	this->InstructionsNumber = (pSize - (sizeof(struct ProgramInfo) + sizeof(struct PrototypeVersion))) / sizeof(struct Instruction);
	this->InstructionPointer = 0;
	this->ExecutedInstructions = 0;
	this->WastedTime = 0;
	this->ConditionalStructuresFound = 0;
	if (this->Instructions == (struct Instruction*)NULL)
		delete[] this->Instructions;
	if (this->ConditionalStructures == (struct Redirections*)NULL)
		delete[] this->ConditionalStructures;
	if (this->ProgramMemory == (Memory*)NULL)
		delete this->ProgramMemory;
	this->Instructions = new struct Instruction[this->InstructionsNumber];
	int j = 0;
	do {
		fread(&this->Instructions[j], sizeof(struct Instruction), 1, pFile);
		j++;
	} while (!feof(pFile));
	fclose(pFile);
	unsigned long x, k, lastUsed = 0;
	for (x = 0; x < InstructionsNumber; x++)
		if ((this->Instructions[x].instruction == (unsigned char)0x07) || (this->Instructions[x].instruction == (unsigned char)0x08))
			this->ConditionalStructuresFound++;
	this->ConditionalStructures = new struct Redirections[this->ConditionalStructuresFound];
	for (x = 0; x < InstructionsNumber; x++)
	{
		if ((this->Instructions[x].instruction == (unsigned char)0x07) || (this->Instructions[x].instruction == (unsigned char)0x08))
		{
			k = x; 
			unsigned long NotEnded = 1;
			do
			{
				k++;
				if (this->Instructions[k].instruction == (unsigned char)0x09)
					NotEnded--;
				if ((this->Instructions[k].instruction == (unsigned char)0x07) || (this->Instructions[k].instruction == (unsigned char)0x08))
					NotEnded++;
			} while (NotEnded != 0);
			this->ConditionalStructures[lastUsed].Begin = x;
			this->ConditionalStructures[lastUsed].End = k;
			if (this->Instructions[x].instruction == (unsigned char)0x07)
				this->ConditionalStructures[lastUsed].Type = (unsigned short)0x00;
			else 
				this->ConditionalStructures[lastUsed].Type = (unsigned short)0x01;
			lastUsed++;
		}
	}
	struct PrototypeVersion CurrentPrototypeVersion = LoadVersionDescriptor();
	if ((this->CompiledPrototypeVersion.major < CurrentPrototypeVersion.major) || ((this->CompiledPrototypeVersion.major == CurrentPrototypeVersion.major) && (this->CompiledPrototypeVersion.minor <= CurrentPrototypeVersion.minor)))
		this->ProgramExecutable = true;
	else
		this->ProgramExecutable = false;
	this->Loaded = true;
}

struct PrototypeVersion* Program::GetCompilerInfo()
{
	if (this->Loaded)
	{
		struct PrototypeVersion* toReturn = (struct PrototypeVersion*)malloc(sizeof(struct PrototypeVersion));
		memcpy(toReturn, &this->CompiledPrototypeVersion, sizeof(struct PrototypeVersion));
		return toReturn;
	} else {
		return (struct PrototypeVersion*)NULL;
	}
}

Program::~Program()
{
	delete[] this->Instructions;
	this->Instructions = (struct Instruction*)NULL;
	delete[] this->ConditionalStructures;
	this->ConditionalStructures = (struct Redirections*)NULL;
	delete this->ProgramMemory;
	this->ProgramMemory = (Memory*)NULL;
}

bool Program::Executable()
{
	return this->ProgramExecutable;
}

bool Program::EndOfInstructions()
{
	if (this->Loaded)
		if (InstructionPointer < InstructionsNumber)
			return false;
		else
			return true;
	else
		return true;
}

void Program::ExecuteInstruction()
{
	if ((this->Loaded) && (InstructionPointer < InstructionsNumber) && (this->ProgramExecutable))
	{
		unsigned long ConditionalStructurePointer;
		int j;
		switch (this->Instructions[this->InstructionPointer].instruction)
		{
			case (unsigned char)0x00:
				this->WastedTime++;
				break;
			case (unsigned char)0x01:
				this->ProgramMemory->SetByte(this->ProgramMemory->GetByte() + this->Instructions[this->InstructionPointer].data);
				break;
			case (unsigned char)0x02:
				this->ProgramMemory->SetByte(this->ProgramMemory->GetByte() - this->Instructions[this->InstructionPointer].data);
				break;
			case (unsigned char)0x03:
				this->ProgramMemory->SetByte(this->ProgramMemory->GetByte() * this->Instructions[this->InstructionPointer].data);
				break;
			case (unsigned char)0x04:
				if (this->Instructions[this->InstructionPointer].data != 0)
					this->ProgramMemory->SetByte(this->ProgramMemory->GetByte() / this->Instructions[this->InstructionPointer].data);
				else
					this->ProgramMemory->SetByte(0x00);
				break;
			case (unsigned char)0x05:
				unsigned int KeyboardInput;
				scanf("%d", &KeyboardInput);
				KeyboardInput = KeyboardInput % 256;
				this->ProgramMemory->SetByte((unsigned char)KeyboardInput);
				break;
			case (unsigned char)0x06:
				printf(" %3d ", this->ProgramMemory->GetByte());
				break;
			case (unsigned char)0x07:
				ConditionalStructurePointer = 0;
				while (this->ConditionalStructures[ConditionalStructurePointer].Begin != this->InstructionPointer)
					ConditionalStructurePointer++;
				if (this->ProgramMemory->GetByte() != this->Instructions[this->InstructionPointer].data)
					this->InstructionPointer = this->ConditionalStructures[ConditionalStructurePointer].End;
				break;
			case (unsigned char)0x08:
				ConditionalStructurePointer = 0;
				while (this->ConditionalStructures[ConditionalStructurePointer].Begin != this->InstructionPointer)
					ConditionalStructurePointer++;
				if (this->ProgramMemory->GetByte() == this->Instructions[this->InstructionPointer].data)
					this->InstructionPointer = this->ConditionalStructures[ConditionalStructurePointer].End;
				break;
			case (unsigned char)0x09:
				ConditionalStructurePointer = 0;
				while (this->ConditionalStructures[ConditionalStructurePointer].End != this->InstructionPointer)
					ConditionalStructurePointer++;
				if ((this->ProgramMemory->GetByte() != this->Instructions[this->InstructionPointer].data) && (this->ConditionalStructures[ConditionalStructurePointer].Type == 0x01))
					this->InstructionPointer = this->ConditionalStructures[ConditionalStructurePointer].Begin;
				break;
			case (unsigned char)0x0A:
				this->ProgramMemory->SetByte(this->Instructions[this->InstructionPointer].data);
				break;
			case (unsigned char)0x0B:
				for (j = 0; j < this->Instructions[this->InstructionPointer].data; j++)
					this->ProgramMemory->GotoNextByte();
				break;
			case (unsigned char)0x0C:
				for (j = 0; j < this->Instructions[this->InstructionPointer].data; j++)
					this->ProgramMemory->GotoPreviousByte();
				break;
			case (unsigned char)0x0D:
				for (j = 0; j < this->Instructions[this->InstructionPointer].data; j++)
					this->ProgramMemory->GotoNextByteList();
				break;
			case (unsigned char)0x0E:
				for (j = 0; j < this->Instructions[this->InstructionPointer].data; j++)
					this->ProgramMemory->GotoPreviousByteList();
				break;
			case (unsigned char)0x0F:
				if (this->ProgramMemory->GetByte() == this->Instructions[this->InstructionPointer].data)
					this->ProgramMemory->SetByte((unsigned char)0x01);
				else
					this->ProgramMemory->SetByte((unsigned char)0x00);
				break;
			case (unsigned char)0x10:
				this->ProgramMemory->POP();
				break;
			case (unsigned char)0x11:
				this->ProgramMemory->PUSH();
				break;
			case (unsigned char)0x12:
				this->ProgramMemory->SetByte(~this->ProgramMemory->GetByte());
				break;
			case (unsigned char)0x13:
				this->ProgramMemory->SetByte(this->ProgramMemory->GetByte() & this->Instructions[this->InstructionPointer].data);
				break;
			case (unsigned char)0x14:
				this->ProgramMemory->SetByte(this->ProgramMemory->GetByte() | this->Instructions[this->InstructionPointer].data);
				break;
			case (unsigned char)0x15:
				this->ProgramMemory->SetByte(this->ProgramMemory->GetByte() ^ this->Instructions[this->InstructionPointer].data);
				break;
			case (unsigned char)0x16:
				this->ProgramMemory->SetByte(~(this->ProgramMemory->GetByte() & this->Instructions[this->InstructionPointer].data));
				break;
			case (unsigned char)0x17:
				this->ProgramMemory->SetByte(~(this->ProgramMemory->GetByte() | this->Instructions[this->InstructionPointer].data));
				break;
			case (unsigned char)0x18:
				this->ProgramMemory->SetByte(~(this->ProgramMemory->GetByte() ^ this->Instructions[this->InstructionPointer].data));
				break;
			case (unsigned char)0x19:
				this->ProgramMemory->SetByte(this->ProgramMemory->GetByte() >> this->Instructions[this->InstructionPointer].data);
				break;
			case (unsigned char)0x20:
				this->ProgramMemory->SetByte(this->ProgramMemory->GetByte() << this->Instructions[this->InstructionPointer].data);
				break;
			default:
				break;
		}
		this->InstructionPointer++;
		this->ExecutedInstructions++;
	}
}

unsigned long Program::GetInstructionsNumber()
{
	if (this->Loaded)
		return this->InstructionsNumber;
	else
		return -1;
}
